/**
 * 
 */
package org.nanhill.commons.dbutil.builder;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * @author alin [xalinx at gmail dot com]
 * @date 2007-5-11
 */
public class BeanMonitor<E> {
	private E bean;

	private E proxy;

	private PropertyDescriptor[] allProperties;

	private List<String> readedPropertyNames;

	private List<String> writedPropertyNames;

	private List<Object> writedPropertyValues;

	private List<Condition> conditions;

	public E getBean() {
		return bean;
	}

	public E getProxy() {
		return proxy;
	}

	public List<String> getReadedPropertyNames() {
		return readedPropertyNames;
	}

	public List<String> getWritedPropertyNames() {
		return writedPropertyNames;
	}

	public List<Object> getWritedPropertyValues() {
		return writedPropertyValues;
	}

	public List<Condition> getConditions() {
		return conditions;
	}

	private void addReaded(String pName) {
		if (null == readedPropertyNames) {
			readedPropertyNames = new ArrayList<String>(allProperties.length * 3 / 2);
		}
		readedPropertyNames.add(pName);
	}

	private void addWrited(String pName, Object value) {
		if (null == writedPropertyNames) {
			writedPropertyNames = new ArrayList<String>(allProperties.length * 3 / 2);
			writedPropertyValues = new ArrayList<Object>(allProperties.length * 3 / 2);
		}
		writedPropertyNames.add(pName);
		writedPropertyValues.add(value);
	}

	private void addCondition(Condition cond) {
		if (null == conditions) {
			conditions = new ArrayList<Condition>(allProperties.length * 3 / 2);
		}
		conditions.add(cond);
	}

	@SuppressWarnings("unchecked")
	public E proxy(E bean) {
		this.bean = bean;
		Class cls = bean.getClass();
		BeanInfo bi;
		try {
			bi = Introspector.getBeanInfo(cls);
		} catch (IntrospectionException e) {
			throw new RuntimeException(e);
		}
		this.allProperties = bi.getPropertyDescriptors();
		this.proxy = (E) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), new BeanHandler());
		return proxy;
	}

	private class BeanHandler implements InvocationHandler {

		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			Object invokeResult = method.invoke(bean, args);
			for (int i = 0; i < allProperties.length; i++) {
				PropertyDescriptor pd = allProperties[i];
				// skip default class property
				if (pd.getName().equals("class")) {
					continue;
				}
				if (null != pd.getReadMethod() && pd.getReadMethod().getName().equals(method.getName())) {
					addReaded(pd.getName());
					break;
				} else if (null != pd.getWriteMethod() && pd.getWriteMethod().getName().equals(method.getName())) {
					// args[0] is suitable?
					addWrited(pd.getName(), args[0]);
					break;
				}
			}
			return invokeResult;
		}

	}

	/**
	 * @return
	 */
	public BeanMonitor<E> like() {
		addCondition(Condition.LIKE);
		return this;
	}

	/**
	 * @return
	 */
	public BeanMonitor<E> and() {
		addCondition(Condition.AND);
		return this;
	}

	/**
	 * @return
	 */
	public BeanMonitor<E> or() {
		addCondition(Condition.OR);
		return this;
	}

	/**
	 * (
	 * 
	 * @return
	 */
	public BeanMonitor<E> start() {
		addCondition(Condition.START);
		return this;
	}

	/**
	 * }
	 * 
	 * @return
	 */
	public BeanMonitor<E> end() {
		addCondition(Condition.END);
		return this;
	}

	/**
	 * =
	 */
	public BeanMonitor<E> eq() {
		addCondition(Condition.EQ);
		return this;
	}

	/**
	 * <=
	 * 
	 * @return
	 */
	public BeanMonitor<E> leeq() {
		addCondition(Condition.LE_EQ);
		return this;
	}

	/**
	 * <
	 * 
	 * @return
	 */
	public BeanMonitor<E> le() {
		addCondition(Condition.LE);
		return this;
	}

	/**
	 * >=
	 * 
	 * @return
	 */
	public BeanMonitor<E> gteq() {
		addCondition(Condition.GT_EQ);
		return this;
	}

	/**
	 * >
	 * 
	 * @return
	 */
	public BeanMonitor<E> gt() {
		addCondition(Condition.GT);
		return this;
	}

	public BeanMonitor<E> isNotNull() {
		addCondition(Condition.IS_NOT_NULL);
		return this;
	}

	public BeanMonitor<E> isNull() {
		addCondition(Condition.IS_NULL);
		return this;
	}

	private int whereOffset = 0;

	/**
	 * 
	 */
	public void where() {
		whereOffset = this.writedPropertyNames.size();
	}

	public int getWhereOffset() {
		return whereOffset;
	}

}
